/*********************************************************************************
 *      Copyright:  (C) 2024 Xu Jiangfeng
 *                  All rights reserved.
 *
 *       Filename:  test_esp.c
 *    Description:  This file test esp.
 *                 
 *        Version:  1.0.0(2024年07月27日)
 *         Author:  Xu Jiangfeng <1922788776@qq.com>
 *      ChangeLog:  1, Release initial version on "2024年07月27日 21时36分15秒"
 *                 
 ********************************************************************************/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <time.h>
#include <sys/time.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <termios.h>
#include <sys/select.h>
#include <pthread.h>
#include <semaphore.h>
#include "modbus.h"

#define REV_OK	   	     0
#define REV_WAIT	     1
#define UART_BUF         128
#define BAUD_RATE        115200
#define MODBUS_DEVICE    "/dev/ttymxc1"
#define UART_DEVICE      "/dev/ttymxc3"
#define WiFi_ID          "Xiaomi_77FA"
#define WiFi_Pass        "913913913"
#define MQTT_USER        "admin"
#define MQTT_PASSWORD    "mja123456"
#define MQTT_SERVER      "127.0.0.1"
#define CLIENT_ID        "85cf8b7aeb184710921b1b1"
#define MQTT_TOPIC       "datas"
#define JSON_FORMAT      "{\\\"voltage\\\":%.3f\\,\\\"current\\\":%.3f\\,\\\"temperature\\\":%.1f\\}"

static unsigned short    esp8266_cnt = 0, esp8266_cntPre = 0;
static unsigned char     buf[UART_BUF] = {0};
static char              modbus_data[256] = {0};
static sem_t             data_ready_sem;
static pthread_mutex_t   data_mutex = PTHREAD_MUTEX_INITIALIZER;
_Bool                    is_server_connected = 0;
static struct            termios old_cfg;
static int               fd;

#define MAX_SLAVES_COUNT  10    // 最多支持从机数
#define INITIAL_LIFETIME  10    // 每个从机的初始访问寿命
#define MAX_LIFETIME      10    // 最大寿命
#define SUCCESS_THRESHOLD 50    // 连续成功读取50次增加一次寿命

#define VOL_SHARP         1.00  // 电压剧烈变化
#define VOL_SLOW          0.50  // 电压缓慢变化
#define CUR_SHARP         1.00	// 电流剧烈变化
#define CUR_SLOW		  0.50	// 电流缓慢变化
#define TEMP_SHARP		  5.0	// 温度剧烈变化
#define TEMP_SLOW         1.0   // 温度缓慢变化
#define DETECT_INTE_MS    300050

#define BROADCAST_INTE_MS 600000    // 每10分钟广播一次时间戳（10 * 60 * 1000）
#define MAX_TIME_MS       864000000 // 主机最大计数10天

typedef struct{
	int slave_addr;             // 从机地址
	int lifetime;               // 读取寿命 
	int is_damaged;             // 是否损坏的标志（1 损坏; 0 正常）
	int success_count;          // 连续成功读取次数
}ModbusSlave;

ModbusSlave slaves[MAX_SLAVES_COUNT];
int      slave_count = 0;         // 判断一开始有无从机接入系统，如果没有从机接入，系统将不断检测所有从机地址。
uint32_t last_detection_time = 0; // 上次检测从机的时间
uint32_t last_broadcast_time = 0; // 上次广播时间

typedef struct uart_hardware_cfg {
	unsigned int         baudrate;  
	unsigned char        dbit;
	char                 parity;
	unsigned char        sbit;
}uart_cfg_t;

static int uart_init(const char *device)
{
	fd = open(device, O_RDWR | O_NOCTTY); 
	if (fd < 0)
	{
		fprintf(stderr, "open error:%s:%s\n", device, strerror(errno));
		return -1;
	}

	if (0 > tcgetattr(fd, &old_cfg)) {
		fprintf(stderr, "tcgetattr error: %s\n", strerror(errno));
		close(fd);
		return -1;
	}

	return 0;
}

static int uart_cfg(const uart_cfg_t *cfg)
{
	struct termios   new_cfg = {0};
	speed_t          speed;

	cfmakeraw(&new_cfg);

	new_cfg.c_cflag |= CREAD;

	switch (cfg->baudrate) {
		case 1200: speed = B1200;
				   break;
		case 1800: speed = B1800;
				   break;
		case 2400: speed = B2400;
				   break;
		case 4800: speed = B4800;
				   break;
		case 9600: speed = B9600;
				   break;
		case 19200: speed = B19200;
					break;
		case 38400: speed = B38400;
					break;
		case 57600: speed = B57600;
					break;
		case 115200: speed = B115200;
					 break;
		case 230400: speed = B230400;
					 break;
		case 460800: speed = B460800;
					 break;
		case 500000: speed = B500000;
					 break;
		default:
					 speed = B115200;
					 printf("default baud rate: 115200\n");
					 break;
	}

	if (0 > cfsetspeed(&new_cfg, speed)) {
		fprintf(stderr, "cfsetspeed error: %s\n", strerror(errno));
		return -1;
	}

	new_cfg.c_cflag &= ~CSIZE;
	switch (cfg->dbit) {
		case 5:
			new_cfg.c_cflag |= CS5;
			break;
		case 6:
			new_cfg.c_cflag |= CS6;
			break;
		case 7:
			new_cfg.c_cflag |= CS7;
			break;
		case 8:
			new_cfg.c_cflag |= CS8;
			break;
		default:
			new_cfg.c_cflag |= CS8;
			break;
	}

	switch (cfg->parity) {
		case 'N':
			new_cfg.c_cflag &= ~PARENB;
			new_cfg.c_iflag &= ~INPCK;
			break;
		case 'O':
			new_cfg.c_cflag |= (PARODD | PARENB);
			new_cfg.c_iflag |= INPCK;
			break;
		case 'E':
			new_cfg.c_cflag |= PARENB;
			new_cfg.c_cflag &= ~PARODD;
			new_cfg.c_iflag |= INPCK;
			break;
		default:
			new_cfg.c_cflag &= ~PARENB;
			new_cfg.c_iflag &= ~INPCK;
			break;
	}

	switch (cfg->sbit) {
		case 1:
			new_cfg.c_cflag &= ~CSTOPB;
			break;
		case 2:
			new_cfg.c_cflag |= CSTOPB;
			break;
		default:
			new_cfg.c_cflag &= ~CSTOPB;
			break;
	}

	new_cfg.c_cc[VTIME] = 0;
	new_cfg.c_cc[VMIN] = 0;

	if (0 > tcflush(fd, TCIOFLUSH)) {
		fprintf(stderr, "tcflush error: %s\n", strerror(errno));
		return -1;
	}

	if (0 > tcsetattr(fd, TCSANOW, &new_cfg)) {
		fprintf(stderr, "tcsetattr error: %s\n", strerror(errno));
		return -1;
	}

	return 0;
}

static void io_handler(int sig, siginfo_t *info, void *context)
{
	if(SIGRTMIN != sig)
		return;

	if (POLL_IN == info->si_code) {
		if (esp8266_cnt >= sizeof(buf))
			esp8266_cnt = 0;

		esp8266_cnt = read(fd, buf, sizeof(buf));

		pthread_mutex_lock(&data_mutex);
		strncpy(modbus_data, (const char*)buf, sizeof(modbus_data) - 1);
		modbus_data[sizeof(modbus_data) - 1] = '\0';
		pthread_mutex_unlock(&data_mutex);

		sem_post(&data_ready_sem);
	}
}

static void async_io_init(void)
{
	struct sigaction sigatn;
	int              flag;

	flag = fcntl(fd, F_GETFL);
	flag |= O_ASYNC;
	fcntl(fd, F_SETFL, flag);
	fcntl(fd, F_SETOWN, getpid());
	fcntl(fd, F_SETSIG, SIGRTMIN);

	sigatn.sa_sigaction = io_handler;
	sigatn.sa_flags = SA_SIGINFO;
	sigemptyset(&sigatn.sa_mask);
	sigaction(SIGRTMIN, &sigatn, NULL);
}

void uart4_init(unsigned int baud, char *device)
{
	uart_cfg_t cfg = {0};

	if (NULL == device) {
		fprintf(stderr, "Error: the device no found!\n");
		exit(EXIT_FAILURE);
	}

	if (uart_init(device))
		exit(EXIT_FAILURE);

	cfg.baudrate = baud;

	if (uart_cfg(&cfg)) {
		tcsetattr(fd, TCSANOW, &old_cfg);
		close(fd);
		exit(EXIT_FAILURE);
	}
}

void esp8266_clear(void)
{
	memset(buf, 0, sizeof(buf));
	esp8266_cnt = 0;
}


_Bool esp8266_waitrecive(void)
{
	if(esp8266_cnt == 0) 							//如果接收计数为0 则说明没有处于接收数据中，所以直接跳出，结束函数
		return REV_WAIT;

	if(esp8266_cnt == esp8266_cntPre)				//如果上一次的值和这次相同，则说明接收完毕
	{
		esp8266_cnt = 0;							//清0接收计数
		return REV_OK;								//返回接收完成标志
	}

	esp8266_cntPre = esp8266_cnt;					//置为相同
	return REV_WAIT;								//返回接收未完成标志
}

_Bool esp8266_sendcmd(char *cmd, char *res, int timeout)
{
	int    ret = 0;

	ret = write(fd, (unsigned char *)cmd, strlen((const char *)cmd));
	if (ret == 0)
	{
		printf("write error!\r\n");
	}

	if(res == NULL || timeout == 0)
	{
		return REV_OK;
	}

	while(timeout > 0)
	{
		if(esp8266_waitrecive() == REV_OK)
		{
			if(strstr((const char *)buf, res) != NULL)
			{
				esp8266_clear();
				return REV_OK;
			}
		}
		timeout--;
		usleep(1000);
	}
	return 1;
}

_Bool esp8266_connectserver(void)
{
	char connect_cmd[512];

	if(is_server_connected)
	{
		printf("Already connected to server.\r\n");
		return 0;
	}

	printf("Connect to server...\r\n");
 	snprintf(connect_cmd, sizeof(connect_cmd), "AT+MQTTUSERCFG=0,1,\"NULL\",\"%s\",\"%s\",0,0,\"\"\r\n", MQTT_USER, MQTT_PASSWORD);
	while(esp8266_sendcmd(connect_cmd, "OK", 1000))
	{
		printf("AT+MQTTUSERCFG is failed!\r\n");
	}

	snprintf(connect_cmd, sizeof(connect_cmd), "AT+MQTTCLIENTID=0,\"%s\"\r\n", CLIENT_ID);
	while(esp8266_sendcmd(connect_cmd, "OK", 1000))
	{
		printf("AT+MQTTCLIENTID is failed!\r\n");
	}

	snprintf(connect_cmd, sizeof(connect_cmd), "AT+MQTTCONN=0,\"192.168.31.103\",1883,1\r\n");
	while(esp8266_sendcmd(connect_cmd, "OK", 1000))
	{
		printf("AT+MQTTCONN is failed!\r\n");
	}

	snprintf(connect_cmd, sizeof(connect_cmd), "AT+MQTTSUB=0,\"%s\",1\r\n", MQTT_TOPIC);
	while(esp8266_sendcmd(connect_cmd, "OK", 1000))
	{
		printf("AT+MQTTSUB is failed!\r\n");
	}

	is_server_connected = 1;
	printf("Connect to server successful!\r\n");

	return 0;
}

_Bool esp8266_connectWifi(const char* ssid, const char* password)
{
	char wifi_buf[256];

	printf("Connect to WiFi...\r\n");
	snprintf(wifi_buf, sizeof(wifi_buf), "AT+CWJAP=\"%s\",\"%s\"\r\n", ssid, password);
	while(esp8266_sendcmd(wifi_buf, "OK", 2000))
	{
		printf("Connect to wifi is failed.\r\n");
	}
	printf("Connect to WiFi OK!\r\n");
	return 0;
}

_Bool check_wifi()
{
	printf("Check WiFi Connection status!\r\n");

	while(esp8266_sendcmd("AT+RST\r\n", "OK", 1000));
	if(esp8266_sendcmd("AT+CWJAP?\r\n", "OK", 1000) == 0)
	{
		printf("Already connected to WiFi.\r\n");
		return 1;
	}
	printf("Not connected to WiFi, need to connect to WiFi.\r\n");
	return 0;
}

void send_data_to_server(const char *topic, const unsigned char *data)
{
	char mqtt_buf[256];

	esp8266_clear();
	snprintf(mqtt_buf, sizeof(mqtt_buf), "AT+MQTTPUB=0,\"%s\",\"%s\",0,0\r\n", topic, data);
	esp8266_sendcmd(mqtt_buf, "OK", 1000);

	return;
}

_Bool esp8266_init()
{
	char  connect_cmd[256];

	esp8266_clear();

	while(esp8266_sendcmd("AT+RESTORE\r\n", "OK", 1000))
	{
		printf("RESTORE is failed!\r\n");
	}

	while(esp8266_sendcmd("AT+RST\r\n", "OK", 500))
	{
		printf("RST is failed!\r\n");
	}

	while(esp8266_sendcmd("ATE0\r\n", "OK", 500))
	{
		printf("ATE0 is failed!\r\n");
	}

	while(esp8266_sendcmd("AT\r\n", "OK", 500))
	{
		printf("AT test is failed!\r\n");
	}

	while(esp8266_sendcmd("AT+CWMODE=1\r\n", "OK", 1000))
	{
		printf("CWMODE=1 is failed!\r\n");
	}

	return 0;
}


// read modbus slave data.
int read_modbus_data(modbus_t *ctx, int slave_addr, uint16_t *tab_reg, int reg_count)
{
	int rc = -1;
	modbus_set_slave(ctx, slave_addr);
	rc = modbus_read_input_registers(ctx, 0, reg_count, tab_reg);
	if (rc == -1)
	{
		fprintf(stderr, "Read failed for slave %d: %s\n", slave_addr, modbus_strerror(errno));
		return -1;
	}
	printf("Data from slave %d:\n", slave_addr);
	for (int i = 0; i < reg_count; i++)
	{
		printf("Reg[%d]=%d\n", i, tab_reg[i]);
	}
	return 0;
}


// 将整数位和小数位组合成浮点型数据
float combine_float(uint16_t int_part, uint16_t frac_part)
{
	char buffer[20];
	snprintf(buffer, sizeof(buffer), "%d.%d", int_part, frac_part);
	return strtof(buffer, NULL);
}

// 计算数据变化的绝对值
float calculate_change(float old_data, float new_data)
{
	return fabs(new_data - old_data);
}

// 检测某个从机是否存在
_Bool is_slave_present(modbus_t *ctx, int slave_addr)
{
	uint16_t tab_reg[2];
	int rc = read_modbus_data(ctx, slave_addr, tab_reg, 1);

	return (rc != -1);
}

// 开机时轮询所有从机地址，初始化从机列表
void initialize_slaves(modbus_t *ctx)
{
	int i;
	int slave_addr;
	for(slave_addr = 1; slave_addr <= MAX_SLAVES_COUNT; slave_addr++)
	{
		if(is_slave_present(ctx, slave_addr))
		{
			slaves[slave_addr - 1].slave_addr = slave_addr;
			slaves[slave_addr - 1].lifetime = INITIAL_LIFETIME;
			slaves[slave_addr - 1].is_damaged = 0;
			slaves[slave_addr - 1].success_count = 0;
			printf("从机 %d 初始化成功\n", slave_addr);
			slave_count++;
		}
	}
}

// 定期检测是否有新的从机接入
void check_new_slaves(modbus_t *ctx)
{
	int i;

	for(int slave_addr = 1; slave_addr <= MAX_SLAVES_COUNT; slave_addr++)
	{
		// 只检查地址为0的位置是否有新从机加入
		_Bool found = 0;
		for(i = 0; i < MAX_SLAVES_COUNT; i++)
		{
			if(slaves[i].slave_addr == slave_addr)
			{
				found = 1;
				break;
			}
		}

		// 如果没有找到该从机，说明是新的，从机加入
		if(!found && is_slave_present(ctx, slave_addr))
		{
			for(i = 0; i < MAX_SLAVES_COUNT; i++)
			{
				if(slaves[i].slave_addr == 0)
				{
					slaves[i].slave_addr = slave_addr;
					slaves[i].lifetime = INITIAL_LIFETIME;
					slaves[i].is_damaged = 0;
					slaves[i].success_count = 0;
					printf("新从机 %d 接入系统\n", slave_addr);
					slave_count++;
					break;
				}
			}
		}
	}
}


uint32_t get_current_time_ms()
{
	struct timeval tv;
	uint32_t time_ms;

	gettimeofday(&tv, NULL);  // 获取当前时间
	time_ms = (uint32_t)(tv.tv_sec) * 1000 + tv.tv_usec / 1000;
	return time_ms % MAX_TIME_MS; // 返回时间戳并确保它不会超过10天
}


void broadcast_timestamp(modbus_t *ctx)
{
	int       rc;
	uint16_t  timestamp_regs[2];
	uint32_t  timestamp;

	timestamp = get_current_time_ms();

	timestamp_regs[0] = timestamp & 0xFFFF;         // 时间戳的低16位
	timestamp_regs[1] = (timestamp >> 16) & 0xFFFF; // 时间戳的高16位

	// 通过广播方式发送时间戳给所有从机
	modbus_set_slave(ctx, 0);
	rc = modbus_write_registers(ctx, 0x00, 2, timestamp_regs);

	printf("广播的时间为：%u\n", timestamp);
}


int main(int argc, char *argv[])
{
	int         baud_rate = BAUD_RATE;
	char        *uart_device = UART_DEVICE;
	modbus_t    *ctx;

	// 初始化上次采集的电压、电流和温度
	float last_vol_data = 0.00;
	float last_cur_data = 0.00;
	float last_temp_data = 0.00;
	int   sleep_time_ms = 10000;   // 默认上报时间间隔10秒

	uart4_init(baud_rate, uart_device);
	async_io_init();

	ctx = modbus_new_rtu(MODBUS_DEVICE, baud_rate, 'N', 8, 1);
	if (ctx == NULL)
	{
		fprintf(stderr, "Unable to create the libmodbus context\n");
		return -1;
	}

	if (modbus_connect(ctx) == -1)
	{
		fprintf(stderr, "Connection failed: %s\n", modbus_strerror(errno));
		modbus_free(ctx);
		return -2;
	}

	// 开机时广播主机时间给从机校准
	uint32_t after_time;

	broadcast_timestamp(ctx);

	after_time = get_current_time_ms();

	printf("广播后的时间为：%u\n", after_time);

	// 开机时初始化所有从机
	initialize_slaves(ctx);

	if(check_wifi() == 0)
	{
		if(esp8266_init() == 0)
		{
			esp8266_connectWifi(WiFi_ID, WiFi_Pass);
			esp8266_connectserver();
		}
	}
	else
	{
		esp8266_connectserver();
	}

	while(1)
	{
		int             i;
		int             rc = -1;
		uint16_t        tab_reg[32];
		char            modbus_data[128];
		uint32_t        current_time;
		uint32_t        before_readtime;
		uint32_t        last_readtime;

		current_time = get_current_time_ms();

		// 当没有从机接入或者满5分钟，检测是否有新的从机接入
		if( !slave_count || (current_time - last_detection_time >= DETECT_INTE_MS) )
		{
			check_new_slaves(ctx);
			last_detection_time = current_time;
		}

		if(current_time - last_broadcast_time >= BROADCAST_INTE_MS)
		{
			broadcast_timestamp(ctx);
			last_broadcast_time = current_time;
		}

		for(i = 0; i < MAX_SLAVES_COUNT; i++)
		{
			ModbusSlave *slave = &slaves[i];

			// 如果地址为0或损坏，则跳过读取该从机数据
			if(slave->slave_addr == 0 || slave->is_damaged)
			{
				continue;
			}

			before_readtime = get_current_time_ms();

		    rc = read_modbus_data(ctx, slave->slave_addr, tab_reg, 7);

			last_readtime = ((uint32_t)tab_reg[5] << 16) | (uint32_t)tab_reg[6];

			printf("主机接收从机 %d 数据的时间差为：%d\n", slave->slave_addr, last_readtime - before_readtime);

			if(rc < 0)
			{
				// 如果读取失败，寿命减1，并重置连续成功计数
				slave->lifetime--;
				slave->success_count = 0;
				printf("从机 %d 读取失败，访问寿命减为：%d\n", slave->slave_addr, slave->lifetime);

				if(slave->lifetime <= 0)
				{
					slave->is_damaged = 1;
					printf("从机 %d 已损坏，将其移除采集系统！\n", slave->slave_addr);
					slave->slave_addr = 0;
					slave_count--;
				}
			}
			else
			{
				// 读取成功，提取数据
				float vol_data = tab_reg[1] / 1000.0;
				float cur_data = tab_reg[2] / 1000.0;
				float temp_data = combine_float(tab_reg[3], tab_reg[4]);
				printf("从机 %d 的电压、电流和温度为: %.3f, %.3f, %.1f\n", tab_reg[0], vol_data, cur_data, temp_data);

				// 计算数据变化
				float vol_change = calculate_change(last_vol_data, vol_data);
				float cur_change = calculate_change(last_cur_data, cur_data);
				float temp_change = calculate_change(last_temp_data, temp_data);

				// 判断电压变化
				if(vol_change > VOL_SHARP || cur_change > CUR_SHARP || temp_change > TEMP_SHARP)
				{
					sleep_time_ms = 100;
				}
				else if(vol_change > VOL_SLOW || cur_change > CUR_SLOW || temp_change > TEMP_SLOW)
				{
					sleep_time_ms = 1000;
				}
				else
				{
					sleep_time_ms = 10000;
				}

				snprintf(modbus_data, sizeof(modbus_data), JSON_FORMAT, vol_data, cur_data, temp_data);

				send_data_to_server(MQTT_TOPIC, (unsigned char*)modbus_data);

				// 更新上次数据
				last_vol_data = vol_data;
				last_cur_data = cur_data;
				last_temp_data = temp_data;

				// 如果读取成功，增加成功计数
				slave->success_count++;

				// 如果连续读取成功次数达到50次，并且寿命小于最大寿命，则增加寿命。
				if(slave->success_count >= SUCCESS_THRESHOLD && slave->lifetime < MAX_LIFETIME)
				{
					slave->lifetime++;
					slave->success_count = 0; // 增加寿命后重置成功计数
				    printf("从机 %d 连续成功读取 %d 次，增加寿命，当前寿命：%d\n", slave->slave_addr, SUCCESS_THRESHOLD, slave->lifetime);
				}
			}

			usleep(sleep_time_ms * 1000);
		}
	}
	
	modbus_close(ctx);
	modbus_free(ctx);

	tcsetattr(fd, TCSANOW, &old_cfg);
	close(fd);

	return 0;
}





